﻿[assembly:
        System.Web.UI.WebResource("BM.Tools.WebControls.MForm.Controls.ValueHandlers.AutoCompleteValueHandler.js",
                "application/x-javascript")]
[assembly:
        System.Web.UI.WebResource("BM.Tools.WebControls.MForm.Controls.ValueHandlers.AutoCompleteValueHandler.debug.js",
                "application/x-javascript")]

namespace BM.Tools.WebControls.MForm.Controls.ValueHandlers
{
    using System;
    using System.ComponentModel;
    using System.Web.Script.Serialization;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;

    using AjaxControlToolkit;

    using BM.Tools.WebControls.MForm.ScriptReferences;

    /// <summary>
    /// The value handler of textbox with autocomplete functionality
    /// </summary>
    [ClientScriptResource("BM.Tools.WebControls.MForm",
            "BM.Tools.WebControls.MForm.Controls.ValueHandlers.AutoCompleteValueHandler.js")]
    [ClientScriptResource("BM.Tools.WebControls.MForm", "BM.Tools.WebControls.MForm.Controls.ExtenderDuplicator.js")]
    public class AutoCompleteValueHandler : TextBoxValueHandler
    {        
        private AutoCompleteExtender extender = new AutoCompleteExtender();

        private HtmlInputHidden hiddenInput;

        private Func<string, string> textForKey;

        /// <summary>
        /// Gets or sets the function that returns the text that should be displayed for the key, if auto complete webservice returns a value and text pair.
        /// </summary>
        public Func<string, string> TextForKey
        {
            get
            {
                return textForKey;
            }

            set
            {
                textForKey = value;
                UpdateControlText(Value);
            }
        }

        /// <summary>
        /// Gets the AutoComplete Extender.
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public AutoCompleteExtender Extender
        {
            get
            {
                return extender;
            }
        }

        /// <summary>
        /// Gets or sets Value.
        /// </summary>
        public override string Value
        {
            get
            {
                if (AcceptResponsesOnly)
                {
                    return HiddenInput.Value;    
                }

                return Control.Text;
            }

            set
            {
                UpdateControlText(value);                
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether only the responses from the webservice should be treated as valid.
        /// </summary>
        [DefaultValue(false)]
        public bool AcceptResponsesOnly
        {
            get;
            set;
        }

        /// <summary>
        /// Gets HiddenInput.
        /// </summary>
        protected HtmlInputHidden HiddenInput
        {
            get
            {
                if (hiddenInput == null)
                {
                    hiddenInput = new HtmlInputHidden();
                    hiddenInput.Attributes.Add("class", "HiddenInput");
                }

                return hiddenInput;
            }
        }

        /// <summary>
        /// Gets the text representation of the key
        /// </summary>
        protected override string DefaultViewModeControlValue
        {
            get
            {
                return AcceptResponsesOnly ? GetTextForKey(Value) : Value;
            }
        }

        /// <summary>
        /// sets the control and extender codes
        /// </summary>
        protected override void CreateChildControls()
        {
            base.CreateChildControls();            
            extender.ID = "AC";
            extender.TargetControlID = Control.ID;            
            Controls.Add(Extender);
            if (AcceptResponsesOnly)
            {
                Controls.Add(HiddenInput);
            }            
        }

        /// <summary>
        /// returns the value from the JSON serialized pair
        /// </summary>
        /// <param name="serializedValue">
        /// the JSON serialized pair
        /// </param>
        /// <returns>
        /// the value from the pair
        /// </returns>
        /// <exception cref="ArgumentException" >
        /// The input length exceeds the maximal value
        /// </exception>        
        /// <exception cref="ArgumentNullException" >
        /// input is null
        /// </exception>
        /// <exception cref="InvalidOperationException" >
        /// the types do not match 
        /// </exception>       
        private static string GetJavascriptSerializedValue(string serializedValue)
        {
            var valuePair = new JavaScriptSerializer().Deserialize<Pair>(serializedValue);
            return valuePair.First.ToString();
        }

        /// <summary>
        /// returns either the first value returned, or if the value is in JSON, returns desrialized value from the first result
        /// </summary>
        /// <param name="values">
        /// the response from the service
        /// </param>
        /// <returns>
        /// the response value
        /// </returns>
        private static string GetValueFromResults(string[] values)
        {
            if (values == null || values.Length == 0)
            {
                return string.Empty;
            }

            try
            {
                return GetJavascriptSerializedValue(values[0]);
            }
            catch (InvalidOperationException)
            {
                return values[0];
            }
        }

        private string GetTextForKey(string key)
        {
            return TextForKey != null ? TextForKey(key) : CallMethod(key);
        }

        /// <summary>
        /// checks if calling the method will bring proper results
        /// </summary>
        /// <param name="value">
        /// the value used as the key
        /// </param>
        /// <returns>
        /// the text of the given value
        /// </returns>
        private string CallMethod(string value)
        {
            if (String.IsNullOrEmpty(value))
            {
                return value;
            }

            try
            {
                return GetAutoCompleteValue(value);
            }
            catch (ScriptServiceInvokerException)
            {
                return value;
            }
        }

        /// <summary>
        /// calls the autocomplete method and expects that the first response is the text if the value was sent
        /// </summary>
        /// <param name="value">
        /// the value used as the key
        /// </param>
        /// <returns>
        /// the matching results
        /// </returns>
        private string[] GetAutoCompleteResults(string value)
        {
            var invoker = new ScriptServiceInvoker(ResolveClientUrl(extender.ServicePath), extender.ServiceMethod);
            invoker.AddParameter("prefixText", value);
            invoker.AddParameter("count", "1");
            return invoker.GetValue<string[]>();
        }

        /// <summary>
        /// gets the valus matching text
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// the matching text
        /// </returns>
        private string GetAutoCompleteValue(string value)
        {
            var results = GetAutoCompleteResults(value);
            return GetValueFromResults(results);
        }

        private void UpdateControlText(string value)
        {
            if (AcceptResponsesOnly)
            {
                HiddenInput.Value = value;
                Control.Text = GetTextForKey(value);
            }
            else
            {
                Control.Text = value;
            }
        }
    }
}